﻿#region Пакеты
using System;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text.RegularExpressions;
#endregion

#region Регулярные Выражения
//(<div class=.post. id=.*?.>)([\s\S]*?)(\n</div>) - Посты
//<a href=.[\s\S]*?. class=.blog_title  blogs.>([\s\S]*?)</a> - Название блога
//<span class=.profiled_blog. title=.Профильный блог.></span> - Профильный ли блог, если найдёт - да
//<a href=.[\s\S]*?. class=.post_title.>([\s\S]*?)</a> - Название поста
//<div class=.content.>([\s\S]*?)(<div class=.habracut.>|<div class=.clear.></div>[\s\S]*?</div>) - Контент поста
//<a class=.habracut. href=.([\s\S]*?).>[\s\S]*?</a> - Линк на хабракат
//<a href=.[\s\S]*?. rel=.tag.>([\s\S]*?)</a> - Тэг
//<div class=.published.>([\s\S]*?)</div> - Дата публикации
//<div class=.favs_count. title=.[\s\S]*?>([0-9]*?)</div> - Сколько людей добавило в избранное
//<div class=.author.>[\s]*?<a title=.[\s\S]*?. href=.[\s\S]*?.>([\s\S]*?)</a>[\s]*?</div> - Автор поста
//<div class=.comments.>[\s]*?<a title=.[\s\S]*?. href=.([\s\S]*?).>[\s\S]*?</div> - Ссылка на комментарии
//^([\s]*)$ - Пустые строки
#endregion

namespace HabraReader
{
    #region Структуры 
    struct Title
    {
        public String BlogTitle;// Название блога, в котором находится пост
        public Boolean ProfiledBlog;// Профильный ли блог?
        public String PostTitle;// Название поста

        public static String ParseBlogTitle(String PostHTMLCode)// Парсим название блога
        {
            Match match = Regex.Match(PostHTMLCode, @"<a href=.[\s\S]*?. class=.blog_title[\s\S]*?.>([\s\S]*?)</a>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            if (match.Success)//если совпадение найдено, возвращаем значение первой группы
            {
                //Groups[1] потому что в Groups[0] лежит весь блок, а не только нужное значение
                return match.Groups[1].Value;
            }
            return null;
        }
        public static Boolean ParseProfiledBlog(String PostHTMLCode)// Проверяем на профильность 
        {
           Match match = Regex.Match(PostHTMLCode, @"<span class=.profiled_blog. title=.Профильный блог.></span>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
           return match.Success;// Если совпадение будет найдено - значит блог профильный
        }
        public static String ParsePostTitle(String PostHTMLCode)// Парсим название поста
        {
            Match match = Regex.Match(PostHTMLCode, @"<a href=.[\s\S]*?. class=.post_title.>([\s\S]*?)</a>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return match.Groups[1].Value;
            }
            return null;
        }
    }    

    struct Content
    {
        public String TextContent;// Сам текст поста
        public String HabraCutLink;// Линк на полную новость

        public static String ParseTextContent(String PostHTMLCode)// Парсим содержание поста (до хабраката)
        {
            Match match = Regex.Match(PostHTMLCode, @"<div class=.content.>([\s\S]*?)(<div class=.habracut.>|<div class=.clear.></div>[\s\S]*?</div>)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return match.Groups[1].Value;
            }
            return null;
        }
        public static String ParseHabraCutLink(String PostHTMLCode)// Получаем ссылку на хабракат
        {
            Match match = Regex.Match(PostHTMLCode,@"<a class=.habracut. href=.([\s\S]*?).>[\s\S]*?</a>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return match.Groups[1].Value;
            }
            return null;
        }
    }

    struct Tag
    {
        public String TagName;        
        // Получаем все теги. Для этого я написал класс наследующий CollectionBase
        public static Tags ParseTags(String PostHTMLCode)
        {            
            MatchCollection matches = Regex.Matches(PostHTMLCode, @"<a href=.[\s\S]*?. rel=.tag.>([\s\S]*?)</a>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (matches.Count != 0)
            {
                Tags tags = new Tags();
                foreach(Match match in matches)
                {
                    tags.AddTag(new Tag() { TagName = match.Groups[1].Value });
                }
                return tags;
            }
            return null;            
        }
    }

    struct InfoPanel
    {
        public String Published;// Когда опубликован?
        public Int16 FavsCount;// Сколько людей добавило в избранное
        public String Author;// Автор поста
        public String CommentsLink;// Ссылка на комментарии

        public static String ParsePublished(String PostHTMLCode)// Парсим дату публикации
        {
            Match match = Regex.Match(PostHTMLCode, @"<div class=.published.>([\s\S]*?)</div>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return match.Groups[1].Value;
            }
            return null;
        }
        public static Int16 ParseFavsCount(String PostHTMLCode)// Парсим количество добавивших в избранное
        {
            Match match = Regex.Match(PostHTMLCode, @"<div class=.favs_count. title=.[\s\S]*?>([0-9]*?)</div>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return Convert.ToInt16(match.Groups[1].Value);
            }
            return 0;
        }
        public static String ParseAuthor(String PostHTMLCode)// Парсим имя автора
        {
            Match match = Regex.Match(PostHTMLCode, @"<div class=.author.>[\s]*?<a title=.[\s\S]*?. href=.[\s\S]*?.>([\s\S]*?)</a>[\s]*?</div>",RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return match.Groups[1].Value;
            }
            return null;
        }
        public static String ParseCommentsLink(String PostHTMLCode)// Парсим ссылку на комментарии
        {
            Match match = Regex.Match(PostHTMLCode, @"<div class=.comments.>[\s]*?<a title=.[\s\S]*?. href=.([\s\S]*?).>[\s\S]*?</div>", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            if (match.Success)
            {
                return match.Groups[1].Value;
            }
            return null;
        }
    }
    
    struct Post
    {
        public Title PostTitle;// Структура названия поста
        public Content PostContent;// Структура контента
        public Tags PostTags;// Массив тэгов
        public InfoPanel PostInfoPanel;// Структура информационной панели

        public static Post ParsePost(String PostHTMLCode)// Метод для получения всего и сразу :)
        {
            Post post = new Post();
            post.PostTitle.BlogTitle = Title.ParseBlogTitle(PostHTMLCode);
            post.PostTitle.PostTitle = Title.ParsePostTitle(PostHTMLCode);
            post.PostTitle.ProfiledBlog = Title.ParseProfiledBlog(PostHTMLCode);

            post.PostContent.TextContent = Content.ParseTextContent(PostHTMLCode);
            post.PostContent.HabraCutLink = Content.ParseHabraCutLink(PostHTMLCode);

            post.PostTags = Tag.ParseTags(PostHTMLCode);

            post.PostInfoPanel.Published = InfoPanel.ParsePublished(PostHTMLCode);
            post.PostInfoPanel.FavsCount = InfoPanel.ParseFavsCount(PostHTMLCode);
            post.PostInfoPanel.CommentsLink = InfoPanel.ParseCommentsLink(PostHTMLCode);
            post.PostInfoPanel.Author = InfoPanel.ParseAuthor(PostHTMLCode);

            return post;
        }
    }
    #endregion

    #region Коллекции
    class Tags : CollectionBase // Массив тэгов
    {
        public Tag this[int index]// Индексатор для "массивного" доступа
        {
            get { return (Tag)List[index]; }
            set { List[index] = value; }
        }

        public void AddTag(Tag tag)// Добавить тэг
        {
            List.Add(tag);
        }
        public void RemoveTag(Tag tag)// Удалить первое вхождение тэга
        {
            List.Remove(tag);
        }
        public void RemoveTagAt(int index)// Удалить тэг с индексом index
        {
            List.RemoveAt(index);
        }
        public void Clear()// Очистить массив тэгов
        {
            List.Clear();
        }
    }

    class Posts : CollectionBase // Массив постов
    {
        public Post this[int index]// Индексатор для "массивного" доступа
        {
            get { return (Post)List[index]; }
            set { List[index] = value; }
        }

        public void AddPost(Post post)// Добавить пост
        {
            List.Add(post);
        }
        public void RemovePost(Post post)// Удалить первое вхождение указанного поста
        {
            List.Remove(post);
        }
        public void RemovePostAt(int index)// Удалить пост с индексом index
        {
            List.RemoveAt(index);
        }
        public void Clear()// Очистить массив постов
        {
            if(List.Count != 0)
                List.Clear();
        }
    }
    #endregion

    #region Парсер
    class HTMLParser
    {        
        public static Posts ParsePosts(String PageHTML)// Получаем посты с страницы
        {
            Posts posts = new Posts();
            PageHTML = RemoveWhiteSpace(PageHTML);// Очищаем код от пустого пространства
            try// На случай непредвиденной ошибки
            {
                foreach (String post in ParsePostsHTML(PageHTML))// Парсим и добавляем посты в масс
                {
                    posts.AddPost(Post.ParsePost(post));//в post находится HTML код поста
                }
            }
            catch (NullReferenceException)
            {
                Box.ShowErrorMessage("HTML Parser error!", "Невозможно разобрать html код. Неверный формат?");
                return null;                
            }
            catch (Exception)
            {
                Box.ShowErrorMessage("HTML Parser error!", "Невозможно разобрать html код. Неверный формат?");
                return null;
            }
            return posts;
        }        
        private static String[] ParsePostsHTML(String HTML)// Парсим html код всех постов на странице
        {
            // Получить код всех постов со страницы
            MatchCollection match_collection = Regex.Matches(HTML, @"(<div class=.post. id=.*?.>)([\s\S]*?)(\n</div>)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            if (match_collection.Count != 0)// Если коллекция не пуста, начинаем собирать массив html кода постов
            {
                String[] PostsHtml = new String[match_collection.Count];
                for (int i = 0; i < match_collection.Count; i++)
                {
                    PostsHtml[i] = match_collection[i].Groups[2].Value;
                }
                return PostsHtml;
            }            
            return null;           
        }        
        private static String RemoveWhiteSpace(String HTML)// Очищаем html от пустых строк
        {
            return Regex.Replace(HTML, @"^([\s]*)$", "", RegexOptions.Multiline | RegexOptions.IgnoreCase);
        }
    }
    #endregion
}
